package ru.eas.tetris;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

/**
 * @author aesipov
 *
 *  To do:
 *  ---------------
 *  * check game over
 *  * remove lines
 *  * add frame per second couter
 *  * right rotates
 *  * draw next figure
 *  * add scores
 *  * figure colors
 *  add other figures
 *  make good design
 *  add music and sound effects
 *  implement second view on javafx
 *  ---------------
 */
public class TetrisTheGame
{
    public static final int  SECOND               = 1000;
    public static final int                 REMOVE_LINE_TIME = 300;
    private final Well                      well;
    private Figure                          figure;
    private Figure                          nextFigure;
    private final FigureGenerator           figureGenerator;
    private int                             scores;

    private LinkedBlockingDeque<GameAction> deque            = new LinkedBlockingDeque<GameAction>();
    private List<Integer>                   completeLines    = Collections.EMPTY_LIST;

    private GameState                       state;
    private Rules                           rules            = new Rules();
    private int                             nextLevelScores;
    private int                             level;
    private long                            fallMillisSleep;

    enum GameState {
        READY_TO_START, STARTED, SHOW_LEVEL, REMOVE_LINES, LEVEL_DONE, GAME_OVER
    }

    enum GameAction {
        MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT, ROTATE_LEFT, ROTATE_RIGHT
    }

    public TetrisTheGame()
    {
        well = new Well(10, 20);
        figureGenerator = new FigureGenerator();
        state = GameState.READY_TO_START;
    }

    void start()
    {
        if (state != GameState.READY_TO_START)
        {
            throw new IllegalStateException();
        }

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                doStart();
            }
        }).start();

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                while (true)
                {
                    deque.addLast(GameAction.MOVE_DOWN);
                    sleep(fallMillisSleep);
                }
            }
        }).start();
    }

    private void doStart()
    {
        scores = 0;
        nextFigure = figureGenerator.next();
        // well.clear();
        level = 1;

        while (true)
        {
            state = GameState.SHOW_LEVEL;
            sleep(2000);
            state = GameState.STARTED;
            if (playLevel())
            {
                state = GameState.LEVEL_DONE;
                sleep(2000);
                level++;
            }
            else
            {
                state = GameState.GAME_OVER;
                return;
            }
        }
    }

    private void setSpeed()
    {
        int fallSpeed = rules.getFallSpeed(level);
        fallMillisSleep = SECOND / fallSpeed;
    }

    private boolean playLevel()
    {
        setSpeed();
        nextLevelScores = rules.getNexeLevelScores(level);

        while (true)
        {
            figure = nextFigure;
            moveFigureToInitPlace(figure);
            nextFigure = figureGenerator.next();

            fallDownFigure();
            well.changeState(figure);

            if (well.isOverflown())
            {
                return false;
            }
            int linesCount = removeLines();
            if (linesCount != 0)
            {
                scores += rules.getScores(linesCount);
                if (scores >= nextLevelScores)
                {
                    return true;
                }
            }
        }
    }

    private int removeLines()
    {
        completeLines = well.getCompleteLines();

        if (completeLines.isEmpty())
        {
            return 0;
        }
        state = GameState.REMOVE_LINES;

        sleep(REMOVE_LINE_TIME);
        well.removeLines(completeLines);

        state = GameState.STARTED;
        return completeLines.size();
    }

    private void sleep(long millis)
    {
        try
        {
            Thread.sleep(millis);
        }
        catch (InterruptedException e)
        {
            throw new IllegalStateException(e);
        }
    }

    private void fallDownFigure()
    {
        clearAllActions();

        while (true)
        {
            switch (getAction())
            {
                case MOVE_DOWN:
                    figure.moveDown();
                    if (isFigureOutOfWell() || checkCollision())
                    {
                        figure.moveUp();
                        return;
                    }
                    break;
                case MOVE_LEFT:
                    figure.moveLeft();
                    if (isFigureOutOfWell() || checkCollision())
                    {
                        figure.moveRight();
                    }
                    break;
                case MOVE_RIGHT:
                    figure.moveRight();
                    if (isFigureOutOfWell() || checkCollision())
                    {
                        figure.moveLeft();
                    }
                    break;
                case ROTATE_LEFT:
                    figure.rotateLeft();
                    if (isFigureOutOfWell() || checkCollision())
                    {
                        figure.rotateRight();
                        break;
                    }
                    break;
                case ROTATE_RIGHT:
                    break;
                default:
                    throw new IllegalStateException();
            }
        }
    }

    private void clearAllActions()
    {
        deque.clear();
    }

    private GameAction getAction()
    {
        try
        {
            return deque.takeFirst();
        }
        catch (InterruptedException e)
        {
            throw new IllegalStateException(e);
        }
    }

    private boolean isFigureOutOfWell()
    {
        return figure.getX() < 0 || figure.getY() < 0 || (figure.getX() + figure.getWidth() > well.getWidth())
               || (figure.getY() + figure.getHeight() > well.getHeight());
    }

    private void moveFigureToInitPlace(Figure figure)
    {
        int centerX = (well.getWidth() - figure.getWidth()) / 2;
        figure.moveTo(centerX, 0);
    }

    private boolean checkCollision()
    {
        for (Square square : figure.getSquares())
        {
            if (well.isSquare(square.x, square.y))
            {
                return true;
            }
        }
        return false;
    }

    public void moveDown()
    {
        deque.addLast(GameAction.MOVE_DOWN);
    }

    public void moveRight()
    {
        deque.addLast(GameAction.MOVE_RIGHT);
    }

    public void moveLeft()
    {
        deque.addLast(GameAction.MOVE_LEFT);
    }

    public void rotateLeft()
    {
        deque.addLast(GameAction.ROTATE_LEFT);
    }

    public void rotateRight()
    {
        deque.addLast(GameAction.ROTATE_RIGHT);
    }

    public Well getWell()
    {
        return well;
    }

    public Figure getFigure()
    {
        return figure;
    }

    public Figure getNextFigure()
    {
        return nextFigure;
    }

    public List<Integer> getCompleteLines()
    {
        return completeLines;
    }

    public int getScores()
    {
        return scores;
    }

    public GameState getState()
    {
        return state;
    }

    public int getLevel()
    {
        return level;
    }

    public int getNextLevelScores()
    {
        return nextLevelScores;
    }

    public static void main(String[] args)
    {
        TetrisTheGame tetrisTheGame = new TetrisTheGame();
        new GameView(tetrisTheGame);
        tetrisTheGame.start();
    }
}
